package com.nulldev.util.uri.client;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.security.PrivateKey;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

import com.nulldev.util.crypto.encryptAPI.aes.AESCipher;
import com.nulldev.util.crypto.encryptAPI.manage.EncryptAPI;
import com.nulldev.util.crypto.encryptAPI.rsa.RSA;
import com.nulldev.util.encoding.base64.Base64;
import com.nulldev.util.io.ByteArrayInOutStream;
import com.nulldev.util.io.DynamicByteArrayInputStream;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.io.IOUtils.ChunkedEvent;
import com.nulldev.util.java.DataSet;
import com.nulldev.util.java.JVMUtil;
import com.nulldev.util.other.ArrayUtils;
import com.nulldev.util.other.ByteUtils;
import com.nulldev.util.string.StringsUtil;
import com.nulldev.util.uri.URIUtil2;
import com.nulldev.util.uri.http.HTTP;
import com.nulldev.util.uri.iwebkit2.secure.iWebKitSecureModes;

/**
 * BETA HttpClient from nullUtil 2.6-build b218
 * @author null
 *
 */
public class HttpClient {
	private static final String iWEBKIT_DEFAULT_SECURE_MODE = iWebKitSecureModes.RSA_2048.getMode();
	public static final String ACCEPTED_ENCODING_BASE = "gzip, deflate";
	public static final String[] ACCEPTED_METHODS = {
		"GET", "POST", "OPTIONS", "DELETE"	
	};
	
	private final URL url;
	private final HttpClientStream handler;
	
	private String method = "GET";
	
	public String getMethod() {
		return this.method;
	}
	
	public void setMethod(final String method) {
		if (method == null) {
			return;
		}
		if (!ArrayUtils.StringArrayUtils.contains(ACCEPTED_METHODS, method)) {
			return;
		}
		this.method = method;
	}
	
	public HttpClient(final URL url, final HttpClientStream handler) {
		if (url == null) {
			throw new RuntimeException("HttpClient.error: URL cannot be null!");
		}else if (handler == null) {
			throw new RuntimeException("HttpClient.error: HttpClientStream cannot be null!");
		}
		this.url = url;
		this.handler = handler;
	}
	
	private boolean enable_iWebKitSecure;
	private String iWebKitSecure_mode = iWEBKIT_DEFAULT_SECURE_MODE;
	
	@Deprecated
	public boolean isWebKitSecureEnabled() {
		return this.enable_iWebKitSecure;
	}
	
	@Deprecated
	public void setWebKitSecureEnabled(final boolean value) {
		this.enable_iWebKitSecure = value;
	}
	
	public String getWebKitSecureMode() {
		return this.iWebKitSecure_mode;
	}
	
	public void setiWebKitSecureMode(final iWebKitSecureModes mode) {
		if (mode == null) {
			return;
		}
		this.iWebKitSecure_mode = mode.getMode();
	}
	
	private Proxy proxy = Proxy.NO_PROXY;
	
	public boolean hasProxy() {
		return this.proxy.equals(Proxy.NO_PROXY);
	}
	
	public Proxy getProxy() {
		return this.proxy;
	}
	
	public void setProxy(final Proxy proxy) {
		if (proxy == null || proxy.type() == Proxy.Type.DIRECT) {
			this.proxy = Proxy.NO_PROXY;
			return;
		}
		this.proxy = proxy;
	}
	
	private String userAgent = URIUtil2.URI_UTIL2_USER_AGENT;
	
	private DataSet<byte[], byte[]> iWebKitSecure_KEYS;
	private String stdKeys;
	
	private boolean throwOnErrorCode = true;
	
	private boolean throwOnErrorCode() {
		return this.throwOnErrorCode;
	}
	
	public void setThrowOnErrorCode(final boolean value) {
		this.throwOnErrorCode = value;
	}
	
	public void run() throws Exception {
		final URLConnection conn0 = new com.nulldev.util.uri._exposed.patched.HttpURLConnection(url, this.proxy);
		if (!(conn0 instanceof HttpURLConnection)) {
			throw new RuntimeException("HttpClient.error: Cannot use non HTTP/HTTPS connection!");
		}
		final HttpURLConnection conn = (HttpURLConnection) conn0;
		conn.setFollowRedirects(true);
		conn.setRequestProperty("User-Agent", (this.compatMode ? 
				"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36"
				: this.userAgent));
		conn.setRequestProperty("Accept-Encoding", ACCEPTED_ENCODING_BASE + (this.enable_iWebKitSecure ? ", iWebKitSecure" : ""));
		if (this.enable_iWebKitSecure) {
			//WARNING: Required base 64, will have byte[]
			this.iWebKitSecure_KEYS = RSA.generateKey();
			this.stdKeys = StringsUtil.randomString(32);
			final String publicKey = Base64.encodeBase64String(this.iWebKitSecure_KEYS.getA());
			conn.setRequestProperty("iWebKit.publicKey", (
					iWebKitSecure_mode.equalsIgnoreCase("iWebKitSecure.RSA_2048") || iWebKitSecure_mode.equalsIgnoreCase("iWebKitSecure.RSA_4096") ? publicKey : this.stdKeys));
			conn.setRequestProperty("iWebKit.Secure.Method", this.iWebKitSecure_mode);
		}
		if (this.compatMode) {
			conn.setRequestProperty("Connection", "keep-alive");
			conn.setRequestProperty("Pragma", "no-cache");
			conn.setRequestProperty("Cache-Control", "no-cache");
			conn.setRequestProperty("Accept", "text/html, application/xml;q=0.9, application/xhtml xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1");
			conn.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
			conn.setRequestProperty("Upgrade-Insecure-Requests", "1");
		}
		conn.connect();
		String encoding = conn.getContentEncoding();
		ArrayUtils.printLln(conn.getHeaderFields());
		if (encoding == null) {
			encoding = conn.getHeaderField("Content-Encoding");
		}
		if (encoding == null && this.enable_iWebKitSecure) {
			encoding = "iWebKitSecure";
		}
		JVMUtil.async_println("Content Encoding: " + encoding);
		JVMUtil.async_println("rmsg: " + conn.getResponseMessage());
		final int resp = conn.getResponseCode();
		JVMUtil.async_println("HF0: " + conn.getHeaderFieldKey(0) + ", " + conn.getHeaderField(0));
		InputStream is;
		if (!HTTP.isOK(resp)) {
			if (this.throwOnErrorCode) {
				throw new RuntimeException("HttpClient ERROR! Error code recieved: " + conn.getResponseCode());
			}
			is = conn.getErrorStream();
		}if (encoding == null) {
			is = conn.getInputStream();
		}else if (encoding.equalsIgnoreCase("gzip")) {
			is = new GZIPInputStream(conn.getInputStream());
		}else if (encoding.equalsIgnoreCase("defalte")) {
			is = new InflaterInputStream(conn.getInputStream(), new Inflater(true));
		}else if (encoding.equalsIgnoreCase("iweb_enc")) {
			//DEPRECATED: No longer used due to issues with large data. Use iWebKitSecure instead
			if (conn.getHeaderField("Content-Encoding-Version") != null) {
				final byte[] data = IOUtils.toByteArray(conn.getInputStream());
				is = new ByteArrayInputStream(EncryptAPI.getEncryptorForEncryption("XXTEA").decryptBytes(data, 
						conn.getHeaderField("iWebKit.decryptKey")));
			}else {
				is = conn.getInputStream();
				final ByteArrayOutputStream baos = new ByteArrayOutputStream();
				byte[] buffer = new byte[4096];
				final String key = conn.getHeaderField("iWebKit.decryptKey");
				while (is.read(buffer) != -1) {
					baos.write(EncryptAPI.getEncryptorForEncryption("XXTEA")
							.decryptBytes(buffer, key));
					baos.flush();
				}
				is.close();
				is = new ByteArrayInputStream(baos.toByteArray());
				baos.close();
			}
		}else if (encoding.equalsIgnoreCase("iWebKitSecure")) {
			String type = conn.getHeaderField("Content-Encoding-Type");
			if (type == null) {
				type = "iWebKitSecure." + this.iWebKitSecure_mode;
			}
			JVMUtil.async_println("Type: " + type);
			if (type == null) {
				throw new RuntimeException("HttpClient.error: Cannot decrypt iWebKit Secure content! [Unknown encryption type]");
			}else if (type.equalsIgnoreCase("iWebKitSecure.RSA_2048") || type.equalsIgnoreCase("iWebKitSecure.RSA_4096")) {
				is = conn.getInputStream();
				final PrivateKey pk = RSA.getPrivateKey(this.iWebKitSecure_KEYS.getB());
				final ByteArrayInOutStream baos = new ByteArrayInOutStream();
				byte[] buffer = new byte[4096];
				while (is.read(buffer) != -1) {
					baos.write(RSA.decrypt(pk, buffer));
					baos.flush();
				}
				is.close();
				is = baos.getInputStream();
			}else if (type.equalsIgnoreCase("iWebKitSecure.AES_256")) {
				final AESCipher aes = new AESCipher(this.stdKeys.getBytes());
				is = conn.getInputStream();
				final ByteArrayInOutStream baos = new ByteArrayInOutStream();
				byte[] buffer = new byte[4096];
				while (is.read(buffer) != -1) {
					baos.write(aes.getDecryptedMessage(buffer));
					baos.flush();
				}
				is.close();
				is = baos.getInputStream();
				baos.close();
			}else {
				throw new UnsupportedOperationException("HttpClient.iWebKitSecure.decryptError: UNKNOWN TYPE. [type='" + type + "']");
			}
		}else {
			is = conn.getInputStream();
		}
		if (is == null) {
			throw new RuntimeException("No input stream provided!");
		}
		//TODO: Handle InputStream
		this.handler.onStream(is, resp);
		conn.disconnect();
	}
	
	private static interface HttpClientStream {
		/**
		 * DO NOT TOUCH
		 */
		public void onStream(final InputStream is, final int responseCode) throws Exception;
	}
	
	public static abstract class HttpClientStringOutput implements HttpClientStream {
		/**
		 * DO NOT TOUCH
		 */
		@Override
		public void onStream(final InputStream is, final int responseCode) throws Exception {	
			this.onData(new String(IOUtils.toByteArray(is)), responseCode);
		}
		
		public abstract void onData(final String data, final int responseCode);
	}
	
	public static abstract class HttpClientByteOutput implements HttpClientStream {
		/**
		 * DO NOT TOUCH
		 */
		@Override
		public void onStream(final InputStream is, final int responseCode) throws Exception {	
			this.onData(IOUtils.toByteArray(is), responseCode);
		}
		
		public abstract void onData(final byte[] data, final int responseCode);
	}
	
	public static abstract class HttpClientStreamOnly implements HttpClientStream {
	}
	
	private boolean compatMode;

	public void compatMode(final boolean value) {
		this.compatMode = value;
	}
	
	public boolean isCompatMode() {
		return this.compatMode;
	}
	
//	public static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)";
//	public String USER_AGENT = DEFAULT_USER_AGENT;
//	
//	public static class HTTPGet {
//		protected int requestResult = HTTP.BAD_REQUEST;
//		protected String result = "";
//		
//		public int getResponseCode() {
//			return this.requestResult;
//		}
//		
//		public String getResult() {
//			return this.result;
//		}
//
//		public void local_set_result(final String set, final int authCode) {
//			if (authCode == 0x0000D) {
//				this.result = set;
//			}			
//		}
//		
//		public void local_set_resultR(final int code, final int authCode) {
//			if (authCode == 0x0000D) {
//				this.requestResult = code;
//			}			
//		}
//	}
//	
//	private DataSet<Integer, String> downloadPage(final URL url, final boolean redirects, final Proxy proxy, final String useragent) throws IOException {
//		final HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
//		conn.setFollowRedirects(redirects);
//		conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
//		conn.setRequestProperty("User-Agent", useragent);
//		final String encoding = conn.getContentEncoding();
//		InputStream inStr = null;
//
//		if (encoding != null && encoding.equalsIgnoreCase("gzip")) {
//		    inStr = new GZIPInputStream(conn.getInputStream());
//		} else if (encoding != null && encoding.equalsIgnoreCase("deflate")) {
//		    inStr = new InflaterInputStream(conn.getInputStream(),
//		      new Inflater(true));
//		} else {
//		    inStr = conn.getInputStream();
//		}
//
//		final String out = IOUtils.getStringFromInputStreamS(inStr);
//		final int resp = conn.getResponseCode();
//		conn.disconnect();
//		return new DataSet<Integer, String>(resp, out);
//	}
//	
//	public HTTPGet queryGet(final URL url) {
//		final HTTPGet out = new HTTPGet();
//		DataSet<Integer, String> response = new DataSet<Integer, String>(-1, "");
//		try {
//			response = downloadPage(url, true, Proxy.NO_PROXY, USER_AGENT);
//		} catch (IOException e) {
//		}		
//		out.requestResult = response.getA();
//		out.result = response.getB();
//		return out;
//	}
}
